package com.eugenes.concurrency;

import com.eugenes.concurrency.callables.SinAdder;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class AddingSinusWithExecutor {

    private static final double EPS = 1e-9;

    private final int nThreads;
    private final int N;
    private final List<Callable<Double>> tasks = new ArrayList<>();
    private List<Future<Double>> futures = new ArrayList<>();
    private final ThreadPoolExecutor executor;

    public AddingSinusWithExecutor(int nThreads, int N) {
        this.nThreads = nThreads;
        this.N = N;
        this.executor = new ThreadPoolExecutor(nThreads, nThreads, 1, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(2 * N + 1));
    }

    private AddingSinusWithExecutor createTasks(int taskSize) {
        for (int i=-N; i<=N; i+=taskSize) {
            SinAdder ss = new SinAdder(i, Math.min(i + taskSize - 1, N));
            tasks.add(ss);
        }
        return this;
    }

    private AddingSinusWithExecutor startTasks() {
        for (Callable<Double> c : tasks) {
            futures.add(executor.submit(c));
        }
        return this;
    }

    public AddingSinusWithExecutor finish() {
        executor.shutdown();
        return this;
    }

    private AddingSinusWithExecutor checkAssertResult(double result) {
        assert (result < EPS) : "Result is wrong";
        return this;
    }

    public double perform() {
        long startTime = System.currentTimeMillis();

        int count = 2 * N + 1;
        int taskSize = count / nThreads + (count % nThreads == 0 ? 0 : 1);

        createTasks(taskSize).startTasks();

        double result = 0.;
        for (Future<Double> f : futures) {
            try {
                result += f.get();
            } catch (ExecutionException | InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        checkAssertResult(result);

        System.err.println("\nResult: " + result + "\n" +
                "Time: " + (System.currentTimeMillis() - startTime) + "ms.\n");

        return result;
    }

    public static void main(String[] args) throws InterruptedException {
        int nThreads;
        int N;

        try {
            nThreads = Integer.parseInt(args[0]);
            N = Integer.parseInt(args[1]);
            if (nThreads <= 0 || nThreads > 64 || N < 0) {
                throw new IllegalArgumentException();
            }
        } catch (Exception ex) {
            System.out.println("Illegal arguments. Try again.\n" +
                    "Arguments: number of threads (>0, <=64) and N (>0).\n");
            return;
        }

        AddingSinusWithExecutor aswe = new AddingSinusWithExecutor(nThreads, N);
        aswe.perform();
        aswe.finish();
    }
}
